package com.jack.framework.common.utils;

import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

/**
 * Created by zhang_j on 2020/7/15
 */
public class ReflectionUtils {

    private static Logger logger = LoggerFactory.getLogger(ReflectionUtils.class);

    /**
     * 直接读取对象属性值，无视private/protected修饰符，不经过getter函数
     */
    public static Object getFieldValue(final Object obj, final String fieldName){
        Field field = getAccessibleField(obj, fieldName);

        if(field == null){
            throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
        }

        Object result = null;
        try{
            result = field.get(obj);
        }catch (IllegalAccessException e){
            logger.error("this exception cannot happen:{}", e.getMessage());
        }
        return result;
    }

    /**
     * 循环向上转型，获取对象的DeclaredField，并强制设置为可访问
     * 如向上转型到Object仍无法找到，返回null
     * @param obj
     * @param fieldName
     * @return
     */
    public static Field getAccessibleField(final Object obj, final String fieldName){
        Validate.notNull(obj, "object can't be null");
        Validate.notBlank(fieldName, "fieldName can't be blank");
        for(Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()){
            try{
                Field field = superClass.getDeclaredField(fieldName);
                makeAccessible(field);
                return field;
            }catch (NoSuchFieldException e){
                //Field不在当前类定义，继续向上转型
                continue;
            }
        }
        return null;
    }

    public static void makeAccessible(Field field){
        boolean needSet = (!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers())
                || Modifier.isFinal(field.getModifiers())) && !field.isAccessible();
        if(needSet){
            field.setAccessible(true);
        }
    }

}
